package com.why.simpleboot.core.core.aop.interceptor;

import com.why.simpleboot.core.annotation.aop.After;
import com.why.simpleboot.core.annotation.aop.Before;
import com.why.simpleboot.core.annotation.aop.Pointcut;
import com.why.simpleboot.core.common.utils.PatternMatchUtils;
import com.why.simpleboot.core.common.utils.SimpleReflectUtils;
import com.why.simpleboot.core.core.aop.entity.JointPoint;
import com.why.simpleboot.core.core.aop.entity.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @program: why-simpleboot-framework
 * @description:每个呗@Aspect标注的类，都会为其生成一个对应的默认拦截器
 * @author: @why
 * @create: 2021-05-27 14:09
 **/

public class DefaultAspectJInterceptor extends Interceptor {
    private static final Logger log = LoggerFactory.getLogger(DefaultAspectJInterceptor.class);
    /**
     * 原拦截器
     */
    private Object adviceObject;
    /**
     * 原拦截器class
     */
    private Class<?> adviceClass;
    /**
     * 前置方法
     */
    private List<Method> beforeMethods = new ArrayList<>();
    /**
     * 后置方法
     */
    private List<Method> afterMethods = new ArrayList<>();
    /**
     * 切入的位置
     */
    private List<String> pointCutUrls = new ArrayList<>();


    public DefaultAspectJInterceptor(Object adviceObject, Class<?> adviceClass) {
        this.adviceObject = adviceObject;
        this.adviceClass = adviceClass;
        init();
    }

    /**
     * 加载切面类
     */
    public void init() {
        for (Method method : adviceObject.getClass().getMethods()) {
            Pointcut pointcut = method.getAnnotation(Pointcut.class);
            //保存当前代理类能够支持切入的范围  切点
            if (Objects.nonNull(pointcut)) {
                pointCutUrls.add(pointcut.value());
            }
            Before before = method.getAnnotation(Before.class);
            if (Objects.nonNull(before)) {
                beforeMethods.add(method);
            }
            After after = method.getAnnotation(After.class);
            if (Objects.nonNull(after)) {
                afterMethods.add(method);
            }
        }

        log.info("support{},{},{}:", afterMethods, beforeMethods, pointCutUrls);
    }


    /**
     * 判断该方法是否应该被切入
     *
     * @param obj
     * @return
     */
    @Override
    public boolean supported(Object obj) {
        return (beforeMethods.size() > 0 || afterMethods.size() > 0)
                && pointCutUrls.stream().anyMatch(url ->
                PatternMatchUtils.simpleMatch(url, obj.getClass().getName()));
    }

    /**
     * 核心方法，执行前置方法  执行目标方法  执行后置方法
     *
     * @param methodInvocation
     * @return
     */
    @Override
    public Object proceed(MethodInvocation methodInvocation) {
//执行前置处理方法

        // JointPoint 保存切点类
        JointPoint defaultJointPoint = new DefaultJointPoint(adviceObject, methodInvocation.getTarget(), methodInvocation.getArgs());
//执行前置通知
        for (Method method : beforeMethods) {
            SimpleReflectUtils.invokeMethod(adviceObject, method, defaultJointPoint);
        }
//执行目标方法
        Object result = methodInvocation.invoke();
//执行后置通知
        for (Method method : afterMethods) {
            SimpleReflectUtils.invokeMethod(adviceObject, method, defaultJointPoint, result);
        }
        return result;
    }

    /**
     * 该类保存了切入的类
     * 执行方法的目标类
     * 执行方法的参数
     */
    private class DefaultJointPoint implements JointPoint {
        private Object adviceObject;
        private Object target;
        private Object[] args;

        public DefaultJointPoint(Object adviceObject, Object target, Object[] args) {
            this.adviceObject = adviceObject;
            this.target = target;
            this.args = args;
        }

        @Override
        public Object getAdviceObject() {
            return adviceObject;
        }

        @Override
        public Object getTarget() {
            return target;
        }

        @Override
        public Object[] getArgs() {
            if (args == null) {
                return new Object[0];
            }
            return args;
        }

    }

}
